
import

	tango.core.Array ,
	tango.core.Thread ,
	tango.core.Traits,
	
	tango.net.Uri,
	tango.math.random.Random,
	tango.net.device.Berkeley,
	tango.net.InternetAddress,
	
	tango.stdc.stringz,
	Int	= tango.text.convert.Integer ,
	
	tango.io.Stdout ,
	tango.io.device.Array ,
	tango.io.device.File ,
	libeventd.Types ;


void main(){
	WSADATA wd = void;
	WSAStartup (0x0202, &wd);
	auto base	= event_base_new();
	//evthread_use_windows_threads();
	//event_base_start_iocp(base);
	
	Http* http	= Http.Init(base) ;
	http.bind("127.0.0.1", 8888);
	
	timeval tv	= {99999999,0} ;
	event_base_loopexit(base, &tv);
	event_base_dispatch(base);
}

struct Http {
	event_base*	_base ;
	evhttp*		_http ;
	evdns_base*	_dns ;
	
	static Http* Init(event_base* base) {
		Http* p	= new Http;
		p._base	= base ;
		p._http	= evhttp_new(base);
		evhttp_set_gencb(p._http, &Http_Gen_Cb, p);
		p._dns	= evdns_base_new(base, 0);
		evdns_base_config_windows_nameservers(p._dns);
		return p;
	}
	
	bool bind(char[] ip, ushort port) {
		int ret	= evhttp_bind_socket(_http, toStringz(ip) , port) ;
		if( ret is 0 ){
			Stdout.formatln("http://{}:{}/", ip, port).flush ;
			return true;
		}
		Stdout.formatln("bind {}:{} failure", ip, port).flush ;
		return false;
	}
	
	void free() {
		if( _http !is null ) {
			evhttp_free(_http);
			_http	= null ;
		}
	}
	
	static extern(C) void Http_Gen_Cb(evhttp_request* req, void* ctx) {
		Http* _this	= cast(Http*) ctx ;
		Stdout.formatln("Http_Gen_Cb:{} ", req );
		auto myl	= new MyLocal;

		myl.http(_this).asServer(req) ;
		myl._fb	= new Fiber( &myl.Start );
		myl._fb.call() ;
	}
}

static char[] tozString(char[] from) {
	char[] to = new char[ from.length + 1];
	memcpy(&to[0], &from[0], from.length );
	to[$-1]	= 0 ;
	return to ;
}

template IRequest(T) {
	Http*			_http	;
	evhttp_request*		_req	;
	evhttp_connection*	_conn ;
	char[]			_url	= null ;
	Fiber				_fb	= null ;
	bool				_isClosed	= false ;
	int				_id ;
	static if( isReferenceType!(T) ){
		alias T	This ;
	}else{
		alias T*	This ;
	}
	
	This url(char[] url) {
		this._url	= tozString(url) ;
		return this ;
	}
	
	This http(Http* http) {
		this._http	= http ;
		return this ;
	}
	char[] url(){
		if( _url !is null ){
			return _url[0..$-1];
		}
		if( _req !is null ) {
			char* uri	= evhttp_request_get_uri(_req ) ;
			return fromStringz(uri);
		}
		return null ;
	}
	
	void free() {
		log(__LINE__,  " free, {}, {} ",  _conn, _req) ;
		if( _conn !is null) {
			log(__LINE__,  " free, {}, {} ",  _conn, _req) ;
			evhttp_connection_free(_conn);
			_conn	= null ;
			_req		= null ;
		}else if( _req !is null) {
			log(__LINE__,  " free, {}, {} ",  _conn, _req) ;
			evhttp_request_free(_req);
			_req		= null ;
		}
	}
	
	void cancel () {
		evhttp_cancel_request(_req);
	}
	
	This timeout(int sec ){
		assert(_conn !is null);
		evhttp_connection_set_timeout(_conn, sec );
		return this;
	}
	
	This trys(int _times){
		assert(_conn !is null);
		evhttp_connection_set_retries(_conn, _times);
		return this;
	}
	
	This asServer(evhttp_request* req) {
		_req		= req ;
		_conn	= evhttp_request_get_connection(req) ;
		evhttp_connection_set_closecb(_conn, &Close_Cb, this);
		timeout(3);
		trys(1);
		_isClosed 	= false ;
		return this;
	}
	
	This asClient( char[] url = null ) {
		if( url !is null ){
			_url	= tozString(url) ;
		}
		scope uri		= new Uri(_url);
		int	port		= uri.port ;
		if( port < 20 ) {
			port	= 80 ;
		}
		scope host	= toStringz( uri.host );
		_conn	= evhttp_connection_base_new(_http._base, _http._dns, host , port) ;
		
		evhttp_connection_set_closecb(_conn, &Close_Cb, this);
		timeout(3);
		trys(1);
		
		_req	= evhttp_request_new(&Read_Cb , this) ;
		
		assert(_fb !is null);
		assert(_fb.state == Fiber.State.EXEC );
		_isClosed 	= false ;
		return this;
	}
	
	This send() {
		evhttp_make_request(_conn, _req, evhttp_cmd_type.EVHTTP_REQ_GET, _url.ptr );
		return this;
	}
	
	static extern(C) void Read_Cb(evhttp_request* req, void* ctx){
		This _this	= cast(This) ctx ;
		assert( _this._req == req ) ;
		if( _this._fb is null ) {
			_this.log(__LINE__,  " empty.fb " ) ;
			return ;
		}
		if( _this._fb.state == Fiber.State.HOLD ) {
			_this.log(__LINE__,  " call " ) ;
			_this._fb.call() ;
			assert( _this._fb !is null);
		}
		if( _this._fb.state == Fiber.State.TERM ) {
			_this.log(__LINE__,  " delete " ) ;
			_this.free() ;
			_this._fb.clear ;
			delete _this ;
		} else {
			_this.log(__LINE__,  " read done " ) ;
		}
	}
	
	static extern(C) void Close_Cb(evhttp_connection* conn, void* ctx){
		This _this		= cast(This) ctx ;
		_this._isClosed	= true ;
		assert( _this._conn == conn ) ;
		if( _this._fb is null ) {
			_this.log(__LINE__,  "empty.fb" ) ;
			return ;
		}
		// HOLD,  EXEC,  TERM
		if( _this._fb.state == Fiber.State.HOLD ){
			_this.log(__LINE__,  "call " ) ;
			_this._fb.call() ;
			assert( _this._fb !is null);
		}
		if( _this._fb.state == Fiber.State.TERM ) {
			_this.log(__LINE__,  "delete " ) ;
			_this._fb.clear ;
			delete _this ;
		} else {
			_this.log(__LINE__,  " closed") ;
		}
	}
	
	void log(T...)(int ln, T t) {
		Thread th	= Thread.getThis ;
		if( _id is 0 ){
			_id	= ++_log_id ;
		}
		Stdout.format("{}:{}:{}:{}:`{}` ", _id, ln,  cast(void*) th, _req, url );
		if( _fb !is null ){
			Stdout.format("state:{} ", _fb.state) ;
		}
		Stdout.formatln(t).flush ;
	}
}

static _log_id	= 0 ;



struct MyLocal {
	mixin IRequest!(MyLocal);

	MyProxy*	myp ;
	void Start() {
		this.log(__LINE__,  " new request") ;
		//free ;
		myp		= new MyProxy ;
		myp._fb	= _fb ;
		
		myp.http(_http).url("http://www.163.com" ~  url ).asClient() ;
		this.log(__LINE__,  "  start fetch") ;
		evbuffer*	ob	= evbuffer_new() ;
		assert(myp._req !is null);
		
		evkeyvalq* lih 	= _req.input_headers ;
		auto oh		= lih.tqh_first ;
		evkeyvalq* roh 	= myp._req.output_headers ;
		while( oh !is null ){
			Stdout(" 	")(fromStringz(oh.key))(": ")(fromStringz(oh.value))(" \n").flush;
			
			if( fromStringz(oh.key) == "Host" ){
				evhttp_add_header(roh, oh.key, "www.baidu.com\0".ptr );
			} else {
				evhttp_add_header(roh, oh.key, oh.value );
			}
			oh	= oh.next.tqe_next ;
		}
		
		myp.send();
		_fb.yield ;
		if( myp._isClosed ) {
			this.log(__LINE__,  "  fetched failure") ;
			// cloase self
			char[] tmp	= "closed ";
			evbuffer_add(ob, tmp.ptr , tmp.length);
			send(HTTP_OK, ob, myp);
			return ;
		}
		assert(myp._req);
		
		evkeyvalq* loh 	= _req.output_headers ;
		evkeyvalq* rih 	= myp._req.input_headers ;
		log(__LINE__, "local heades:{}", loh );
		auto ih	= rih.tqh_first ;
		while( ih !is null ){
			Stdout(" ")(fromStringz(ih.key))(": ")(fromStringz(ih.value))(" \n").flush;
			evhttp_add_header(loh, ih.key, ih.value );
			ih	= ih.next.tqe_next ;
		}
		evbuffer* ib	= evhttp_request_get_input_buffer( myp._req );
		this.log(__LINE__,  "  fetched ok") ;
		ubyte[1024] tmp ;
		while(true) {
			int i	= evbuffer_remove(ib, tmp.ptr, tmp.length );
			if( i is 0 || i >  tmp.length ){
				break;
			}
			//Stdout(cast(char[]) tmp[0..i] ).flush ;
			evbuffer_add(ob, tmp.ptr , i);
		}
		send(HTTP_OK, ob, myp);
	}
	
	void send(int ret_code , evbuffer* ob, MyProxy* myp ){
		this.log(__LINE__,  "ret_code:{}", ret_code ) ;
		evbuffer_add_cb( ob, &My_Write_Cb, this);
		evhttp_send_reply(_req, ret_code, "Everything is fine\0".ptr , ob ) ;
		_fb.yield;
		// need put fiber to ob event
		this.log(__LINE__,  "all  data to local client, free local" ) ;
		free;
		this.log(__LINE__,  "all  data to local client, free proxy" ) ;
		myp.free;
	}
	
	static extern(C) void My_Write_Cb(evbuffer *buffer, evbuffer_cb_info *info, void *ctx){
		This _this	= cast(This) ctx;
		if( _this._fb is null ) {
			_this.log(__LINE__,  "empty.fb" ) ;
			return ;
		}
		// HOLD,  EXEC,  TERM
		if( _this._fb.state == Fiber.State.HOLD ){
			_this.log(__LINE__,  "call " ) ;
			_this._fb.call() ;
			assert( _this._fb !is null);
		}
		if( _this._fb.state == Fiber.State.TERM ) {
			_this.log(__LINE__,  "free _this " ) ;
			_this._fb.clear ;
			_this._fb	= null ;
			_this.free ;
		} else {
			_this.log(__LINE__,  " closed") ;
		}
	}
}

struct MyProxy {
	mixin IRequest!(MyProxy) ;
}

